//INSTANT C# NOTE: Formerly VB.NET project-level imports:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Diagnostics;
using System.Windows.Forms;

namespace PowerComboBox
{
	public class xComboBox : ComboBox
	{
//APIs
	[System.Runtime.InteropServices.DllImport("user32.dll")]
	private extern static int SendMessage(System.IntPtr hWnd, int Msg, System.Int32 wParam, System.IntPtr lParam);
//properties
	private string mDividerFormat = "";
	private Color mGroupColor = System.Drawing.SystemColors.WindowText;
	private System.Windows.Forms.CheckState[] mItemsChecks;
	private System.Windows.Forms.CheckState[] mItemsChecks_Temp;
	private bool mCheckBoxes;
	private System.Windows.Forms.CheckState mChecked = CheckState.Unchecked;
	private Color mGridColor = Color.FromArgb(240, 248, 255);
//vars - last selected item
	private char mItemSeparator1 = ',';
    private char mItemSeparator2 = ',';
	//private char mItemSeparator2 = '&';
	private Int32 mHoverIndex;
	private double mHoverIndex_Dec;
//events
	public delegate void ItemHoverEventHandler(Int32 eIndex);
	public event ItemHoverEventHandler ItemHover;
	public delegate void ItemCheckedEventHandler(Int32 eIndex);
	public event ItemCheckedEventHandler ItemChecked;
//vars
	private Int32 mLastSelectedIndex = -1;
	private Timer mTimer;
	private Int32 mFirerTimer;
	private Int32 mKillEvents1; // kills _SelectectedIndexChange ON ForceRedraw
	private Int32 mKillEvents2; // kills _OnDrawItem ON FroceRedraw
	private Int32 mKillEvents3; // kills .ItemsChecks ON Click Selecting (not programatic selecting of .ItemsChecks)
	private Int32 mLastMessage;


//constructor
	public xComboBox()
	{
		this.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;
	}
//properties
	[System.ComponentModel.Description("Use this property to set divider flag.  Recommend you use three hyphens ---."), System.ComponentModel.Category("Power Properties")]
	public string DividerFormat
	{
	get
	{
		return mDividerFormat;
	}
	set
	{
		mDividerFormat = value;
	}
	}
	[System.ComponentModel.Description("Use this property to set the ForeColor of the grouping text."), System.ComponentModel.Category("Power Properties")]
	public Color GroupColor
	{
	get
	{
		return mGroupColor;
	}
	set
	{
		mGroupColor = value;
	}
	}
	[System.ComponentModel.Description("Use this property to set the BackColor of the grid."), System.ComponentModel.Category("Power Properties")]
	public Color GridColor
	{
	get
	{
		return mGridColor;
	}
	set
	{
		mGridColor = value;
	}
	}
//ORIGINAL LINE: Public Property ItemsChecks(ByVal xIndex As Int32) As System.Windows.Forms.CheckState
//INSTANT C# NOTE: C# does not support parameterized properties - the following property has been divided into two methods:
	[System.ComponentModel.Description("Use this property to get/set corresponding checkboc values."), System.ComponentModel.Category("Power Properties")]
	public System.Windows.Forms.CheckState get_ItemsChecks(Int32 xIndex)
	{
		try
		{
		return mItemsChecks[xIndex];
		}
		catch 
		{
		return CheckState.Unchecked;
		}
	}
		public void set_ItemsChecks(Int32 xIndex, System.Windows.Forms.CheckState value)
		{

			if (xIndex < 0 | xIndex > this.Items.Count)
			{
				return;
			}

			if (mKillEvents3 != 0)
			{
			mItemsChecks[xIndex] = value;
			if (ItemChecked != null)
				ItemChecked(SelectedIndex);
			}
			else
			{
				if (this.CheckBoxes)
				{
					if (this.DroppedDown)
					{
						if (this.SelectedIndex == xIndex)
						{
							this.SelectedIndex = -1;
						}
					this.SelectedIndex = xIndex;
					PrepareTimer();
					if (ItemChecked != null)
						ItemChecked(SelectedIndex); //raises event immeadiatly
					}
					else
					{

						if (mItemsChecks == null)
						{
						Array.Resize(ref mItemsChecks_Temp, this.Items.Count);
						mItemsChecks_Temp[xIndex] = value;
						CommitCheckList();
						}
						else
						{
						mItemsChecks[xIndex] = value;
						}

					}
				}
			}

		}
	[System.ComponentModel.Description("Use this property to enable checkboxes."), System.ComponentModel.Category("Power Properties")]
	public bool CheckBoxes
	{
		get
		{
		return mCheckBoxes;
		}
		set
		{
		mCheckBoxes = value;
		}
	}
	[System.ComponentModel.Description("Use this property to set CheckBox's default value."), System.ComponentModel.Category("Power Properties")]
	public System.Windows.Forms.CheckState Checked
	{
		get
		{
		return mChecked;
		}
		set
		{
		mChecked = value;
		}
	}
	[System.ComponentModel.Description("Use this property to set item separator1 character."), System.ComponentModel.Category("Power Properties")]
	public char ItemSeparator1
	{
		get
		{
		return mItemSeparator1;
		}
		set
		{
		mItemSeparator1 = value;
		}
	}
	[System.ComponentModel.Description("Use this property to set item separator2 character."), System.ComponentModel.Category("Power Properties")]
	public char ItemSeparator2
	{
		get
		{
		return mItemSeparator2;
		}
		set
		{
		mItemSeparator2 = value;
		}
	}
//overrides
	protected override void OnSelectedIndexChanged(System.EventArgs e)
	{
	Int32 i = 0;
		if (mKillEvents1 != 0 || SelectedIndex == -1)
		{
			return;
		}
			if (this.DividerFormat.Length > 0 && IsItemAGroup(SelectedIndex))
			{
				if (! this.CheckBoxes)
				{
				this.SelectedIndex = mLastSelectedIndex;
				return;
				}
				else
				{
				mKillEvents3 += 1;
				i = SelectedIndex;
					do
					{
					i += 1;
						if (i > (this.Items.Count - 1))
						{
							break;
						}
						if (IsItemAGroup(i))
						{
							break;
						}
					this.SelectedIndex = i;
					} while (true);
				mKillEvents3 -= 1;
				base.OnSelectedIndexChanged(e);
				return;
				}
			}
		//2 - standard event stuff
		mLastSelectedIndex = this.SelectedIndex;
		base.OnSelectedIndexChanged(e);
			//3 - toggle checkbox/force redraw
			if (this.CheckBoxes && SelectedIndex > -1)
			{
			mKillEvents3 += 1;
				if (this.get_ItemsChecks(this.SelectedIndex) == CheckState.Checked)
				{
				this.set_ItemsChecks(this.SelectedIndex, CheckState.Unchecked);
				}
				else
				{
				this.set_ItemsChecks(this.SelectedIndex, CheckState.Checked);
				}
			mKillEvents3 -= 1;
			PrepareTimer();
			}
	}
	protected override void OnDrawItem(DrawItemEventArgs e)
	{
	Int32 zX1 = 0;
	Pen zPen = null;
	float zWidth = 0F;
	string zText = null;
	Font zFont = null;
	Color zFore = new Color();
	System.Windows.Forms.VisualStyles.CheckBoxState zState = 0;
			//1 - Exit
			if (e.Index < 0)
			{
			base.OnDrawItem(e);
			return;
			}
			//2 - Grouping
			if (this.Items[e.Index].ToString().Contains(this.mDividerFormat) & mDividerFormat.Length > 0)
			{
			e.DrawBackground();
				if (DataSource == null) //TN Engineers
				{
				zText = this.Items[e.Index].ToString();
				}
				else
				{
				zText = ((DataRowView)(this.Items[e.Index]))[this.DisplayMember].ToString();
				}
			zText = this.Items[e.Index].ToString();
			zText = " " + zText.Replace(this.mDividerFormat, "") + " ";
			zFont = new Font(Font, FontStyle.Bold);
				if (e.BackColor == System.Drawing.SystemColors.Highlight)
				{
				zFore = Color.Gainsboro;
				}
				else
				{
				zFore = this.GroupColor;
				}
			zPen = new Pen(zFore);
			zWidth = e.Graphics.MeasureString(zText, zFont).Width;
			zX1 = Convert.ToInt32(e.Bounds.Width - zWidth) / 2;
			e.Graphics.DrawRectangle(zPen, new Rectangle(e.Bounds.X, e.Bounds.Y + e.Bounds.Height / 2, zX1, 1));
			e.Graphics.DrawRectangle(zPen, new Rectangle(e.Bounds.Width - zX1, e.Bounds.Y + e.Bounds.Height / 2, e.Bounds.Width, 1));
			e.Graphics.DrawString(zText, zFont, new SolidBrush(zFore), zX1, e.Bounds.Top);
			}
			else
			{
				//3 - ItemBackColor
				if (mKillEvents2 == 0)
				{
//INSTANT C# NOTE: The following VB 'Select Case' included range-type or non-constant 'Case' expressions and was converted to C# 'if-else' logic:
//					Select Case true
//ORIGINAL LINE: Case System.Convert.ToBoolean(e.State And DrawItemState.Selected)
					if (true == System.Convert.ToBoolean(e.State & DrawItemState.Selected))
					{
					e.DrawBackground();
					}
//ORIGINAL LINE: Case e.Index % 2 = 0
					else if (true == (e.Index % 2 == 0))
					{
					e.Graphics.FillRectangle(new SolidBrush(Color.White), e.Bounds);
					}
//ORIGINAL LINE: Case Else
					else
					{
					e.Graphics.FillRectangle(new SolidBrush(mGridColor), e.Bounds);
					}
				}
				//4 - ItemText ( _SearchPoint1)
				if (this.DataSource == null) //TN Engineers
				{
				e.Graphics.DrawString(this.Items[e.Index].ToString(), Font, new SolidBrush(e.ForeColor), e.Bounds.Left, e.Bounds.Top);
				}
				else
				{
				e.Graphics.DrawString(((DataRowView)(this.Items[e.Index]))[this.DisplayMember].ToString(), Font, new SolidBrush(e.ForeColor), e.Bounds.Left, e.Bounds.Top);
				}
				//5 - CheckBox
				if (mCheckBoxes)
				{
						if (System.Convert.ToBoolean(e.State & DrawItemState.Selected))
						{
							if (this.get_ItemsChecks(e.Index) == CheckState.Checked)
							{
							zState = System.Windows.Forms.VisualStyles.CheckBoxState.CheckedHot;
							}
							else
							{
							zState = System.Windows.Forms.VisualStyles.CheckBoxState.UncheckedHot;
							}
						}
						else
						{
							if (this.get_ItemsChecks(e.Index) == CheckState.Checked)
							{
							zState = System.Windows.Forms.VisualStyles.CheckBoxState.CheckedNormal;
							}
							else
							{
							zState = System.Windows.Forms.VisualStyles.CheckBoxState.UncheckedNormal;
							}
						}
				zX1 = this.FontHeight;
				zPen = new Pen(Color.Black, 1F);
				zPen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
				System.Windows.Forms.CheckBoxRenderer.DrawCheckBox(e.Graphics, new System.Drawing.Point(e.Bounds.X + e.Bounds.Width - 15, e.Bounds.Y + 1 + ((e.Bounds.Height - 13) / 2)), e.Bounds, "", this.Font, false, zState);
				}
			}
		//6 - Base event
		base.OnDrawItem(e);
	}
	protected override void WndProc(ref System.Windows.Forms.Message m)
	{
	const Int32 WM_SETCURSOR = 32;
	const Int32 WM_COMMAND = 273;
	const int WM_CTLCOLORLISTBOX = 308;
	const Int32 CB_ADDSTRING = 323;
	const Int32 CB_GETCURSEL = 327;
	const Int32 WM_LBUTTONUP = 514;
	const Int32 OCM_COMMAND = 8465;
//INSTANT C# NOTE: The following VB 'Select Case' included range-type or non-constant 'Case' expressions and was converted to C# 'if-else' logic:
//			Select Case true
//ORIGINAL LINE: Case m.Msg = WM_CTLCOLORLISTBOX Or m.Msg = WM_SETCURSOR
			if (true == (m.Msg == WM_CTLCOLORLISTBOX || m.Msg == WM_SETCURSOR))
			{
			GetHoverIndexFromCursor();
				if (mHoverIndex_Dec >= 0)
				{
					if (mHoverIndex > -1 & mHoverIndex < this.Items.Count & this.DroppedDown)
					{
					if (ItemHover != null)
						ItemHover(mHoverIndex);
					}
				}
			}
//ORIGINAL LINE: Case m.Msg = CB_ADDSTRING
			else if (true == (m.Msg == CB_ADDSTRING))
			{
			base.WndProc(ref m);
			StretchCheckList();
			return;
			}
//ORIGINAL LINE: Case m.Msg = WM_COMMAND AndAlso m.WParam = new System.IntPtr(66536)
			else if (true == (m.Msg == WM_COMMAND && m.WParam == new System.IntPtr(66536)))
			{
				if (mLastMessage == 8235) //FIX for ignoring SIC event on keyboard controlling i.e. F4 + down arrow
				{
				mHoverIndex = this.SelectedIndex;
				if (ItemHover != null)
					ItemHover(mHoverIndex);
				mLastMessage = m.Msg;
				return;
				}
				//1 - normal behaviour when no checkboxes
				if (! this.mCheckBoxes)
				{
				base.WndProc(ref m);
				return;
				}
				//2 - (NEW) nulls MouseWheel on not .DropDown
				if (! this.DroppedDown)
				{
				return;
				}
			//3 - ClickEvent reconstruction (cancelled) child events from WM_COMMAND MSG
			SendMessage(this.Handle, OCM_COMMAND, 591814, new IntPtr(1705748)); //1
			SendMessage(this.Handle, OCM_COMMAND, 67526, new IntPtr(1705748)); //2 SelectedIndexChange
			SendMessage(this.Handle, CB_GETCURSEL, 0, new IntPtr(0)); //3
			SendMessage(this.Handle, WM_LBUTTONUP, 0, new IntPtr(721012)); //4
			//4 - cancels event
			return;
			}

		mLastMessage = m.Msg;
		base.WndProc(ref m);

	}
	protected override void OnKeyDown(System.Windows.Forms.KeyEventArgs e) //FIX for keyboard handling
	{

			if (this.DroppedDown && e.KeyCode == Keys.Space)
			{
			mKillEvents3 += 1;
			e.SuppressKeyPress = true;
				if (this.CheckBoxes)
				{
					if (this.get_ItemsChecks(this.SelectedIndex) == CheckState.Checked)
					{
					this.set_ItemsChecks(this.SelectedIndex, CheckState.Unchecked);
					}
					else
					{
					this.set_ItemsChecks(this.SelectedIndex, CheckState.Checked);
					}
				mKillEvents3 -= 1;
				}
				else
				{
				this.DroppedDown = false;
				}
			PrepareTimer();
			}

		base.OnKeyDown(e);

	}
    //public object DataSource //TN Engineers
    //{
    //get
    //{
    //    return base.DataSource;
    //}
    //set
    //{
    //    mKillEvents1 += 1;
    //    base.DataSource = value;
    //    mKillEvents1 -= 1;
    //}
    // }

//subs
	private void PrepareTimer()
	{
		mTimer = null;
		mTimer = new Timer();
		mTimer.Interval = 64;
		mTimer.Enabled = true;
		mTimer.Tick += mTimer_Tick;
		mFirerTimer = 1;
	}
	private void mTimer_Tick(object sender, System.EventArgs e)
	{
		if (mFirerTimer > 0)
		{
		mTimer.Enabled = false;
		mTimer.Dispose();
		ForceRedraw();
		this.Text = FormatCheckString();
		mFirerTimer = 0;
		}
	}
	public void StretchCheckList()
	{
	Int32 i = 0;
		Array.Resize(ref mItemsChecks, this.Items.Count);
			//1. suck in temp
			if (mItemsChecks_Temp != null)
			{
			mItemsChecks = mItemsChecks_Temp;
			mItemsChecks_Temp = null;
			//2. formats list with pre-defined format
			}
			else
			{
				if (mChecked != CheckState.Checked)
				{
					return;
				}
				for (i = 0; i <= mItemsChecks.GetUpperBound(0); i++)
				{
					if (! (IsItemAGroup(i)))
					{
						mItemsChecks[i] = CheckState.Checked;
					}
				}
			}
		this.Text = FormatCheckString();
	}
	public void CommitCheckList()
	{
		mItemsChecks = mItemsChecks_Temp;
		this.Text = FormatCheckString();
	}
//private
	private void GetHoverIndexFromCursor()
	{
	Int32 yPos = 0;
		yPos = this.PointToClient(System.Windows.Forms.Cursor.Position).Y;
			if (this.DropDownStyle == ComboBoxStyle.Simple)
			{
			yPos -= (this.ItemHeight + 10);
			}
			else
			{
			yPos -= (this.Size.Height + 1);
			}
		mHoverIndex_Dec = yPos / (double)this.ItemHeight;
		mHoverIndex = System.Convert.ToInt32(Math.Floor(mHoverIndex_Dec));
	}
	private void ForceRedraw()
	{
			if (this.Items.Count > 0)
			{
			mKillEvents1 += 1;
			mKillEvents2 += 1;
			this.SelectedIndex -= 1;
			mKillEvents2 -= 1;
			this.SelectedIndex += 1;
			mKillEvents1 -= 1;
			}
	}
	private string FormatCheckString()
	{
//NOTE returns "a,b & c" for internal use
	Int32 i = 0;
	System.Text.StringBuilder sb = new System.Text.StringBuilder("");
	string s = null;
	string zFirst = null;
	string zLast = null;
	Int32 zLastComa = 0;
	string zSpace = " ";
			if (! this.CheckBoxes)
			{
				return this.Text;
			}
			for (i = 0; i < this.Items.Count; i++)
			{
				if (IsItemAGroup(i))
				{
					continue;
				}
				if (this.get_ItemsChecks(i) == CheckState.Checked)
				{
					if (DataSource == null) //TN Engineers
					{
					sb.Append(this.Items[i].ToString());
					}
					else
					{
					sb.Append(((DataRowView)(this.Items[i]))[this.DisplayMember].ToString());
					}
				sb.Append(mItemSeparator1);
				}
			}
		s = sb.ToString();
			if (s.Length == 0)
			{
				return "";
			}
		s = s.Substring(0, s.Length - 1);
		zLastComa = s.LastIndexOf(mItemSeparator1);
			if (zLastComa != -1)
			{
			zLast = s.Substring(zLastComa);
			zFirst = s.Substring(0, zLastComa);
			s = zFirst + zSpace + zLast.Replace(Convert.ToString(mItemSeparator1), Convert.ToString(mItemSeparator2) + zSpace);
			return s;
			}
			else
			{
			return s + " ";
			}
	}
	private bool IsItemAGroup(Int32 xIndex)
	{
			if (this.mDividerFormat.Length > 0 && this.Items[xIndex].ToString().Contains(this.mDividerFormat))
			{
				return true;
			}
//INSTANT C# NOTE: Inserted the following 'return' since all code paths must return a value in C#:
			return false;
	}
//public
	public string CHECKED2CSV()
	{
//NOTE returns "a,b,c" for external user
	Int32 i = 0;
	System.Text.StringBuilder sb = new System.Text.StringBuilder("");
	string s = null;
			for (i = 0; i < this.Items.Count; i++)
			{
				if (IsItemAGroup(i))
				{
					continue;
				}
				if (this.get_ItemsChecks(i) == CheckState.Checked)
				{
					if (DataSource == null) //TN Engineers
					{
					sb.Append(this.Items[i].ToString() + ",");
					}
					else
					{
					sb.Append(((DataRowView)(this.Items[i]))[this.ValueMember].ToString() + ",");
					}
				}
			}
		s = sb.ToString();
			if (s.Length > 0)
			{
				s = s.Substring(0, s.Length - 1);
			}
		return s;
	}

	public string CHECKED2IDCSV()
	{
		return CHECKED2IDCSV(true);
	}

//INSTANT C# NOTE: Overloaded method(s) are created above to convert the following method having optional parameters:
//ORIGINAL LINE: Public Function CHECKED2IDCSV(Optional ByVal xZeroBound As Boolean = true) As String
	public string CHECKED2IDCSV(bool xZeroBound)
	{
	Int32 i = 0;
	Int32 c = 0;
	Int32 a = 0;
	System.Text.StringBuilder sb = new System.Text.StringBuilder("");
	string s = null;
			if (xZeroBound)
			{
				a = 0;
			}
			else
			{
				a = 1;
			}
			for (i = 0; i < this.Items.Count; i++)
			{
				if (IsItemAGroup(i))
				{
					continue;
				}
				if (this.get_ItemsChecks(i) == CheckState.Checked)
				{
					if (this.mDividerFormat.Length > 0)
					{
					sb.Append((c + a).ToString() + ",");
					}
					else
					{
					sb.Append((i + a).ToString() + ",");
					}
				}

			c += 1;
			}
		s = sb.ToString();
			if (s.Length > 0)
			{
				s = s.Substring(0, s.Length - 1);
			}
		return s;
	}
	public string CHECKED2BITSHIFT()
	{
	Int32 i = 0;
	Int32 c = 1;
	Int32 a = 0;
			for (i = 0; i < this.Items.Count; i++)
			{
				if (IsItemAGroup(i))
				{
					continue;
				}
				if (this.get_ItemsChecks(i) != CheckState.Checked)
				{
				a += c;
				}
			c = (c * 2);
			}
		return a.ToString();
	}
	public void CheckAll(CheckState xChecked)
	{
	Int32 i = 0;
			for (i = 0; i < this.Items.Count; i++)
			{
			this.set_ItemsChecks(i, xChecked);
			}
		this.Text = FormatCheckString();
	}


	}
} //end of root namespace